/* Copyright (c) 2010, David Kreuter. See LICENSE for more information */

#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Value_Input.H>
#include <FL/Fl_Progress.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/Fl_Check_Button.H>
#include <FL/gl.h>
#include <math.h>

extern "C"{
#include "headers.h"
}

using namespace std;
const double PI=3.141592654;

// The following class only contains code for camera and setup. The code for
// rendering the objects is in the global function draw_scene and gl.c

void draw_scene();
Fl_Check_Button *real_alpha;

class GLView:public Fl_Gl_Window{
public:
	float cx,cy,cz,ud,lr; int mx,my,coords,wire;
	GLView(int X,int Y,int W,int H,const char *title):
		Fl_Gl_Window(X,Y,W,H,title){
		cx=cy=ud=lr=wire=0;cz=10;
		coords=1;
	}
	void vip(){
		double cnear=0.001,cfar=10000.0,fov=0.9,ratio,sqrt_ratio;

		if(h())ratio=(double)w()/(double)h();
		sqrt_ratio=sqrt(ratio);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glViewport(0,0,w(),h());
		glFrustum(-cnear*fov*sqrt_ratio,
		           cnear*fov*sqrt_ratio,
		          -cnear*fov/sqrt_ratio,
		           cnear*fov/sqrt_ratio, cnear, cfar);

		glEnable(GL_DEPTH_TEST);
		glEnable(GL_CULL_FACE);
		glEnable(GL_TEXTURE_2D);
		if(real_alpha->value()){
			glDisable(GL_ALPHA_TEST);
			glEnable(GL_BLEND);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}else{
			glDisable(GL_BLEND);
			glEnable(GL_ALPHA_TEST);
			glAlphaFunc(GL_GREATER, 0.0f);
		}
	}
	void draw() {
		if (!valid()){valid(1);vip();}

		glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glRotatef(ud, 1.0f, 0.0f, 0.0f);
		glRotatef(lr, 0.0f, 1.0f, 0.0f);
		glTranslatef(-cx,-cy,-cz);

		if(coords){
			glBindTexture(GL_TEXTURE_2D,0);
			glBegin(GL_LINES);
				glColor3f(1.0f,0.0f,0.0f);
					glVertex3f( 1.0f, 0.0f, 0.0f);
					glVertex3f(-1.0f, 0.0f, 0.0f);
				glColor3f(0.0f,1.0f,0.0f);
					glVertex3f( 0.0f, 1.0f, 0.0f);
					glVertex3f( 0.0f,-1.0f, 0.0f);
				glColor3f(0.0f,0.0f,1.0f);
					glVertex3f( 0.0f, 0.0f, 1.0f);
					glVertex3f( 0.0f, 0.0f,-1.0f);
			glEnd();
		}
		if(wire){
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			glDisable(GL_TEXTURE_2D);
		}
		draw_scene();
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glEnable(GL_TEXTURE_2D);
	}
	void resize(int X,int Y,int W,int H){
		Fl_Gl_Window::resize(X,Y,W,H);
		vip();redraw();
	}
	int handle(int event){
		switch(event){
		case FL_RELEASE:
		case FL_DRAG: lr-=(mx-Fl::event_x())/2;
		              ud-=(my-Fl::event_y())/2;
		case FL_PUSH: mx=Fl::event_x(); my=Fl::event_y();
		              if(ud>90)ud=90;   if(ud<-90)ud=-90;
		              redraw();
		case FL_FOCUS: case FL_UNFOCUS: return 1;
		case FL_KEYBOARD:{
			float s_lr=sin(lr/180*PI);
			float c_lr=cos(lr/180*PI);
			float s_ud=sin(ud/180*PI);
			float c_ud=cos(ud/180*PI);
			float factor=0.2f;
			switch(Fl::event_key()){
			case 's': factor*=-1; case 'w':
				cx+=factor*s_lr*c_ud;
				cy-=factor*s_ud;
				cz-=factor*c_lr*c_ud;
				redraw();
				return 1;
			case 'd': factor*=-1; case 'a':
				cx-=factor*c_lr;
				cz-=factor*s_lr;
				redraw();
				return 1;
			case 'q': wire=!wire;
				redraw();
				return 1;
			}
		}
		default: return Fl_Gl_Window::handle(event);
		}
	}
};

#define LOCK_START if(apilock)return; apilock=1;
#define LOCK_END   apilock=0;

Fl_Window *win1, *win2;
GLView *gl;
Fl_Check_Button *halfsized;
Fl_Check_Button *export_all;
// Fl_Check_Button *real_alpha; // I copied this line above the class
Fl_Progress *progress;
#define TYPE(num,pre,type,name) \
	Fl_Button *  load_##name##_button; \
	Fl_Button *  save_##name##_button; \
	Fl_Button *export_##name##_button; \
	Fl_Value_Input     *name##_chooser; \
	narc *pre##n; \
	type **pre; \
	uint32 pre##nr;
#include "gen4_ed.def"

static uint8 apilock=0;

void draw_scene(){
	LOCK_START

	if(r&&m){
		for(uint32 mx=0;mx<r[rnr]->w;mx++)
			for(uint32 my=0;my<r[rnr]->h;my++){
				char looking_for[17];
				sprintf(looking_for,"%s%02d_%02dc",
					r[rnr]->name,mx,my);
				glPushMatrix();
				glTranslatef(mx*8,0,my*8);
				for(uint16 cmnr=0;cmnr<mn->num_files;cmnr++){
					if(!strcmp(m[cmnr]->b->m->name,looking_for)){
						draw_map(m[cmnr],x?x[xnr]:0,b,halfsized->value());
					}
				}
				glPopMatrix();
			}	
	}else if(m){
		draw_map(m[mnr],x?x[xnr]:0,b,halfsized->value());
	}else if(b){
		draw_bmd(b[bnr]);
	}else{
		//Nothing
	}

	//draw_cleanup();
	LOCK_END
}

void chooser_callback(Fl_Widget*, void*){
	#define TYPE(num,pre,type,name) pre##nr=name##_chooser->value();
	#include "gen4_ed.def"
	draw_cleanup();
	gl->redraw();
}

#define UNLOADER(pre,type) \
	if(pre##n){ \
		for(uint32 i=0;i<pre##n->num_files;i++) \
			free_##type(pre[i]); \
		free(pre); \
		pre=(type**)0; \
	}

void load_med(narc **n, void ***v, Fl_Value_Input *v_chooser, const char *name, 
	void (*free_func)(void *), void* (*read_func)(uint8 *, uint32)){

	LOCK_START
	if(*n){
		for(uint32 i=0;i<(*n)->num_files;i++)
			free_func((*v)[i]);
		free(*v);
		v=(void***)0;
	}
	Fl_File_Chooser chooser(".","*.narc",Fl_File_Chooser::SINGLE,
		"Select your narc");
	chooser.preview(0);
	chooser.show();	while(chooser.shown())Fl::wait();
	if(*n)free_narc(*n);
	uint8 *buf=0; uint32 len;
	if(read_file((char*)chooser.value(),&buf,&len)){
		*n=read_narc(buf,len);
		v_chooser->maximum((*n)->num_files-1);
		free(buf);
	}else{
		*n=0;
		v_chooser->maximum(0);
		LOCK_END;
		return;
	}
	*v=(void**)calloc(sizeof(void*),(*n)->num_files);
	int count=(*n)->num_files;
	//count=count>400?400:count;
	progress->maximum(count-1);
	for(uint32 i=0;i<count;i++){
		progress->value(i); Fl::flush(); DEBUG("%d. %s\n",i,name);
		(*v)[i]=read_func((*n)->file[i],(*n)->size[i]);
	}
	LOCK_END
	gl->redraw();
}

void save_med(narc **n, void ***v, Fl_Value_Input *v_chooser, const char *name,
	uint32 (*size_func)(void *), int (*write_func)(void *, uint8 *, uint32)){

	if(!*n)return;
	LOCK_START
	Fl_File_Chooser chooser(".","*.narc",Fl_File_Chooser::CREATE,
		"Select your narc");
	chooser.preview(0);
	chooser.show();	while(chooser.shown())Fl::wait();
	progress->maximum((*n)->num_files-1);
	uint8 *buf; uint32 len;
	for(uint32 i=0;i<(*n)->num_files;i++){
		progress->value(i); Fl::flush(); DEBUG("%d. %s\n",i,name);
		len=size_func((*v)[i]); buf=(uint8*)malloc(len);
		memset(buf,0xE2,len);
		write_func((*v)[i],buf,len);
		save_file(*n, i, buf, len);
	}
	len=size_narc(*n);
	buf=(uint8*)malloc(len);
	if(write_narc(*n,buf,len)){
		write_file((char*)chooser.value(),&buf,&len);
	}
	free(buf);
	LOCK_END
}

void export_med(narc **n, void ***v, Fl_Value_Input *v_chooser,
	int (*export_func)(void *, char *)){
	LOCK_START
	Fl_File_Chooser chooser(".","*",Fl_File_Chooser::CREATE,
		"Export");
	chooser.preview(0);
	chooser.show();	while(chooser.shown())Fl::wait();
	if(!chooser.value()){LOCK_END return;}
	if(export_all->value()){
		char *fn=(char*)malloc(strlen(chooser.value())+5);
		progress->maximum((*n)->num_files-1);
		for(uint32 i=0;i<(*n)->num_files;i++){
			progress->value(i); Fl::flush();
			sprintf(fn,"%s-%04d",chooser.value(),i);
			export_func((*v)[i],fn);
		}
	}else{
		export_func((*v)[(uint32)v_chooser->value()],
			(char*)chooser.value());
	}
	LOCK_END
}

#define TYPE(num,pre,type,name) \
	void load_##name(Fl_Widget*, void*){ \
		load_med(&pre##n,(void***)&pre,name##_chooser,#type, \
			(void  (*)(void *))        &free_##type, \
			(void* (*)(uint8*, uint32))&read_##type); \
	} \
	void save_##name(Fl_Widget*, void*){ \
		save_med(&pre##n,(void***)&pre,name##_chooser,#type, \
			(uint32 (*)(void *))                &size_ ##type, \
			(int    (*)(void *, uint8*, uint32))&write_##type); \
	} \
	void export_##name(Fl_Widget*, void*){ \
		export_med(&pre##n,(void***)&pre,name##_chooser, \
			(int (*)(void *, char*))&export_##type); \
	}
#include "gen4_ed.def"

int main() {
	mn=(narc*)0;
	bn=(narc*)0;
	win1=new Fl_Window(400, 400,"3D-View");
		gl=new GLView(0, 0, 400, 400, "");
		win1->resizable(gl);
	win1->end();
	win1->show();
	win2=new Fl_Window(400, 120,"Poke-Tools");
	#define CH(x) x->bounds(0,0);x->precision(0); \
		x->callback(chooser_callback);
	#define TYPE(num,pre,type,name) \
		  load_##name##_button=new Fl_Button(num*100, 0,100,20,#name); \
		  load_##name##_button->callback(  load_##name); \
		  save_##name##_button=new Fl_Button(num*100,20,100,20,"save"); \
		  save_##name##_button->callback(  save_##name); \
		export_##name##_button=new Fl_Button(num*100,60,100,20,"export"); \
		export_##name##_button->callback(export_##name); \
		\
		name##_chooser=new Fl_Value_Input(num*100,40,100,20); \
		CH(name##_chooser);
	#include "gen4_ed.def"
		halfsized=new Fl_Check_Button(0,80,100,20,"Halfsized");
			halfsized->callback(chooser_callback);
			// the chooser callback redraws the 3d-view
		real_alpha=new Fl_Check_Button(100,80,100,20,"Real Alpha");
			real_alpha->callback(chooser_callback);
		export_all=new Fl_Check_Button(200,80,100,20,"Export all");
		progress=new Fl_Progress(0,100,400,20);
	win2->end();
	win2->show();
	Fl::run();
	draw_cleanup();
	delete gl,progress,win1,win2;
	delete halfsized,real_alpha,export_all;
	#define TYPE(num,pre,type,name) \
		delete load_##name##_button; \
		delete save_##name##_button; \
		delete export_##name##_button; \
		delete name##_chooser; \
		UNLOADER(pre,type)
	#include "gen4_ed.def"
}
